/*
 * Copyright (c) 2016 Brian Swetland
 *
 * Use of this source code is governed by a MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT
 */

#include <app.h>
#include <lk/debug.h>
#include <lk/reg.h>
#include <kernel/thread.h>
#include <kernel/event.h>
#include <arch/arm/cm.h>

#include <driverlib/prcm.h>
#include <driverlib/rfc.h>
#include <driverlib/rf_mailbox.h>
#include <inc/hw_rfc_dbell.h>

#include <rf_patches/rf_patch_cpe_genfsk.h>

#include <platform/radio.h>

#define RADIO_POLLED_MODE 0

#define CPE0_MASK (IRQ_BOOT_DONE | IRQ_RX_OK | IRQ_LAST_COMMAND_DONE | IRQ_COMMAND_DONE)

static event_t ack_evt = EVENT_INITIAL_VALUE(ack_evt, 0, EVENT_FLAG_AUTOUNSIGNAL);

static event_t cpe0_evt = EVENT_INITIAL_VALUE(cpe0_evt, 0, EVENT_FLAG_AUTOUNSIGNAL);

void ti_cc_rfc_cpe_0_irq(void) {
    arm_cm_irq_entry();
    event_signal(&cpe0_evt, false);

    // disable IRQ until thread handles and re-enables them in response to event
    NVIC_DisableIRQ(rfc_cpe_0_IRQn);

    // reschedule if we woke a thread (indicated by !signaled)
    arm_cm_irq_exit(!cpe0_evt.signaled);
}

static inline uint32_t cpe0_reason(void) {
    uint32_t n = HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFCPEIFG) & CPE0_MASK;
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFCPEIFG) = ~n;
    return n;
}

static inline uint32_t cpe0_wait_irq(void) {
    NVIC_EnableIRQ(rfc_cpe_0_IRQn);
    event_wait(&cpe0_evt);
    return cpe0_reason();
}

void ti_cc_rfc_cpe_1_irq(void) {
    arm_cm_irq_entry();
}

void ti_cc_rfc_cmd_ack_irq(void) {
    arm_cm_irq_entry();
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG) = 0;
    event_signal(&ack_evt, false);
    // reschedule if we woke a thread (indicated by !signaled)
    arm_cm_irq_exit(!ack_evt.signaled);
}

uint32_t radio_send_cmd(uint32_t cmd) {
#if RADIO_POLLED_MODE
    while (HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDR) != 0) {}
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG) = 0;
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDR) = cmd;
    while (!HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG)) {}
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG) = 0;
    return HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDSTA);
#else
    while (HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDR) != 0) {}
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG) = 0;
    event_unsignal(&ack_evt);
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDR) = cmd;
    event_wait(&ack_evt);
#endif
    return HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDSTA);
}

void radio_wait_cmd(uint16_t *status) {
    uint32_t addr = (uint32_t) status;
    uint16_t val;
#if RADIO_POLLED_MODE
    for (;;) {
        val = *REG16(addr);
        if (val < 3) {
            // idle, waiting to start, or running
            thread_yield();
        } else {
            break;
        }
    }
#else
    for (;;) {
        uint32_t x = cpe0_wait_irq();
        val = *REG16(addr);
        if (val > 3) {
            break;
        }
    }
#endif
    if ((val != 0x0400) && (val != 0x3400)) {
        dprintf(INFO, "Cmd Status %04x\n", val);
    }
}

void radio_init(void) {
#if !RADIO_POLLED_MODE
    // route all IRQs to CPE0
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFCPEISL) = 0;
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFCPEIEN) = CPE0_MASK;

    // clear any pending
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFCPEIFG) = 0;
    HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG) = 0;

    //NVIC_EnableIRQ(rfc_cpe_0_IRQn);
    //NVIC_EnableIRQ(rfc_cpe_1_IRQn);
    NVIC_EnableIRQ(rfc_cmd_ack_IRQn);
#endif

    // Power RF domain
    PRCMPowerDomainOn(PRCM_DOMAIN_RFCORE);
    while (PRCMPowerDomainStatus(PRCM_DOMAIN_RFCORE) != PRCM_DOMAIN_POWER_ON) ;
    dprintf(INFO, "power on\n");

    // enable the RF top clock
    PRCMDomainEnable(PRCM_DOMAIN_RFCORE);
    PRCMLoadSet();
    dprintf(INFO, "top clock on\n");

    // enable all RF sub clocks
    RFCClockEnable();
    dprintf(INFO, "clocks on\n");

    thread_sleep(1000);
    rf_patch_cpe_genfsk();
    dprintf(INFO, "patched\n");

    unsigned n = radio_send_cmd(IMM_CMD(CMD_PING, 0, 0));
    dprintf(INFO, "RESPONSE %08x\n", n);
    n = radio_send_cmd(IMM_CMD(CMD_PING, 0, 0));
    dprintf(INFO, "RESPONSE %08x\n", n);

    rf_op_fw_info_t fwinfo;
    memset(&fwinfo, 0, sizeof(fwinfo));
    fwinfo.cmd = CMD_GET_FW_INFO;
    n = radio_send_cmd((uint32_t) &fwinfo);
    dprintf(INFO, "FW %d %04x %04x %04x %04x\n",
            n, fwinfo.version, fwinfo.free_ram_start,
            fwinfo.free_ram_size, fwinfo.avail_rat_ch);

    n = radio_send_cmd(IMM_CMD(CMD_START_RAT, 0, 0));
    dprintf(INFO, "START RAT %d\n", n);
}
